package pl.edu.agh.mgr.hotel.routing.algorithms.simple;

import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;

import org.apache.log4j.Logger;

import pl.edu.agh.mgr.hotel.entities.Statistic;
import pl.edu.agh.mgr.hotel.hibernate.HibernateDAO;
import pl.edu.agh.mgr.hotel.ws.client.DataRequest;
import pl.edu.agh.mgr.hotel.ws.client.DataResponse;
import pl.edu.agh.mgr.hotel.ws.client.Query;
import pl.edu.agh.mgr.hotel.ws.client.ServiceDescription;
import pl.edu.agh.mgr.hotel.ws.configuration.NextPeerAlg;
import pl.edu.agh.mgr.hotel.ws.configuration.WSUtil;

public class P2P implements IRoutingAlgorithm {

	private String wsName;
	private Logger log = Logger.getLogger("P2P-" + wsName);
	private Set<ServiceDescription> visitedPeers;
	private LinkedHashSet<ServiceDescription> peersToVisit;
	private List<DataResponse> dataResponses;
	private Statistic stat;
	private LinkedHashSet<ServiceDescription> newPeers;
	private Map<String, Integer> availableResults = new HashMap<>();

	public P2P(String wsName) {
		this.wsName = wsName;
		availableResults.put("C1", 3);
		availableResults.put("C2", 2);
		availableResults.put("C3", 2);
		availableResults.put("C4", 2);
		availableResults.put("C5", 1);
	}

	@Override
	public List<DataResponse> getResults(DataRequest dataRequest,
			P2PType p2pType) {

		dataResponses = new ArrayList<DataResponse>();
		visitedPeers = new HashSet<ServiceDescription>();
		peersToVisit = new LinkedHashSet<ServiceDescription>();
		stat = new Statistic();
		// //////////////////////
		stat.setWsName(dataRequest.getName());
		stat.setAlgorithm("FS35 1 11 40 extra data data level");
		stat.setHops(-1);
		stat.setHopsLimit(100);
		stat.setResultLimit(100);
//		 stat.setTimeLimit(2500000000L);
		stat.setTimeLimit(9999999999L);
		stat.setExperimentDate();
		stat.setAvailabelResults(availableResults.get(getSearchedCityName(dataRequest)));
		// /////////////////////

		// get peers according to statistics or just random
		newPeers = WSUtil.getPeersCache(wsName).getPeers(NextPeerAlg.RANDOM);
		
		// pre search based on serviceDescription
		LinkedHashSet<ServiceDescription> extraDataSet = WSUtil.getPeersCache(wsName).getPeersBasedOnExtraData(dataRequest.getQueries());
		
		if (!extraDataSet.isEmpty())
			newPeers = extraDataSet;			
		
		stat.setStart();
		log.info("Peers cache size = " + newPeers.size());
		stat.setCacheSize(newPeers.size());
		DataResponse dataResponse = null;
		boolean isNewPeersSetAvailable = true;

		if (p2pType.equals(P2PType.BFS)) {
			while (isNewPeersSetAvailable && isStatisticSatisfied()) {

				stat.incHop();
				peersToVisit.addAll(newPeers);
				for (ServiceDescription visitedPeer : visitedPeers) {
					if (peersToVisit.contains(visitedPeer)) {
						peersToVisit.remove(visitedPeer);
					}
				}

				newPeers = new LinkedHashSet<ServiceDescription>();
				for (ServiceDescription visitedPeer : peersToVisit) {
					// log.info(wsName + " visiting : "
					// + visitedPeer.getWsAddress());
					
					stat.incSearchedNodes();
					dataResponse = WSUtil.getWSClient(
							visitedPeer.getWsAddress()).getHotelData(
							dataRequest);

					// statistics
//					WSUtil.getPeersCache(wsName).updateDegreeStat(visitedPeer,
//							dataResponse.getServicesDescriptions().size());
//					WSUtil.getPeersCache(wsName).updateHopsStat(visitedPeer,
//							stat.getHops());
//					WSUtil.getPeersCache(wsName).updateTimeStat(visitedPeer,
//							simplePeerStop - simplePeerStart);

					// eval response
					if (dataResponse.getHotel() != null) {
						dataResponses.add(dataResponse);
						stat.incFoundResults();
//						WSUtil.getPeersCache(wsName).updateAnswersStat(
//								visitedPeer);
						for (Query query : dataResponse.getQueries()) {
							if (query.getAttribute().equals(
									visitedPeer.getName() + "ChangeDate")) {
								visitedPeer.setLastModifiedDateAsDate(new Date(
										Long.parseLong(query.getValue())));
							}
							if (query.getAttribute().equals(
									visitedPeer.getName() + "DataLevel")) {
								visitedPeer.setDataLevel(Integer.parseInt(query
										.getValue()));
								log.info("Got data leve " + query.getValue());
							}
						}

//					} else if (!dataResponse.getQueries().isEmpty()) {
//						stat.incFoundResults();
//						for (Query query : dataResponse.getQueries()) {
//							if (query.getAttribute().equals(
//									visitedPeer.getName() + "ChangeDate")) {
//								log.info("No data change");
//								dataResponses.add(dataResponse);
//							}
//						}
					} else {
						newPeers.addAll(dataResponse.getServicesDescriptions());
					}

					// FIXME : if limit riched stop searching
//					if (dataResponses.size() == stat.getResultLimit())
//						break;
				}

				// remove our web service from searching list
				Iterator<ServiceDescription> it = newPeers.iterator();
				for (; it.hasNext();) {
					ServiceDescription peer = it.next();
					if (peer.getWsAddress().equals(wsName)) {
						newPeers.remove(peer);
						break;
					}
				}

				visitedPeers.addAll(peersToVisit);
				peersToVisit = new LinkedHashSet<ServiceDescription>();

				if (newPeers.isEmpty()) {
					isNewPeersSetAvailable = false;
				}
			}
		} else if (p2pType.equals(P2PType.DFS)) {
			for (ServiceDescription peer : newPeers)
				dfsVisit(dataRequest, peer);
		}
		stat.setStop();
		stat.setFoundPercent();
		
		// time limit on
//		stat.setTime(stat.getStop() - stat.getStart());
//		if (stat.getTime() > stat.getTimeLimit()) {
//			dataResponses = new ArrayList<DataResponse>();
//			stat.setTime(2500000000L);
//			stat.setFoundResults(0.0);
//			stat.setFoundPercent();
//		} 

		// result found or not
		if (dataResponses.isEmpty())
			stat.setResult(false);
		else
			stat.setResult(true);

		// FIXME: no cache
		WSUtil.getPeersCache(wsName).addPeers(visitedPeers);

		stat.setTime(stat.getStop() - stat.getStart());
		log.info("Seraching time = " + (stat.getTime()));

//		log.info("Answers stats");
//		WSUtil.getPeersCache(wsName).printStatistics(
//				WSUtil.getPeersCache(wsName).getAnswersStat());

//		log.info("Degree stats");
//		WSUtil.getPeersCache(wsName).printStatistics(
//				WSUtil.getPeersCache(wsName).getDegreeStat());
////
//		log.info("Hops stats");
//		WSUtil.getPeersCache(wsName).printStatistics(
//				WSUtil.getPeersCache(wsName).getHopsStat());
//

		HibernateDAO<Statistic> statDAO = new HibernateDAO<>(Statistic.class);
		statDAO.persist(stat);

		return dataResponses;
	}

	private void dfsVisit(DataRequest dataRequest,
			ServiceDescription visitedPeer) {
		if (!visitedPeers.contains(visitedPeer) && isStatisticSatisfied()) {
			DataResponse dataResponse = WSUtil.getWSClient(
					visitedPeer.getWsAddress()).getHotelData(dataRequest);
			visitedPeers.add(visitedPeer);

			if (dataResponse.getHotel() != null) {
				dataResponses.add(dataResponse);
			} else {
				stat.incHop();
				for (ServiceDescription peer : dataResponse
						.getServicesDescriptions()) {
					dfsVisit(dataRequest, peer);
				}
			}
		}
	}

	private boolean isStatisticSatisfied() {
		return (stat.getHops() < stat.getHopsLimit())
				&& (dataResponses.size() < stat.getResultLimit());
	}
	
	private String getSearchedCityName(DataRequest dataRequest) {
		for (Query query : dataRequest.getQueries())
			if (query.getAttribute().equals("city"))
				return query.getValue();
		throw new NoSuchElementException();
	}

}
